package evm

import (
	"bytes"
	"encoding/hex"
	"fmt"
	"math/big"

	"gitee.com/caivega/easm/core"
	"github.com/ethereum/go-ethereum/common"
	"github.com/ethereum/go-ethereum/core/state"
	"github.com/ethereum/go-ethereum/core/types"
	"github.com/ethereum/go-ethereum/core/vm"
	"github.com/ethereum/go-ethereum/params"
	"github.com/holiman/uint256"
)

type print struct{}

func (c *print) RequiredGas(input []byte) uint64 {
	return 1000
}

func (c *print) Run(input []byte) ([]byte, error) {
	fmt.Println(">", hex.EncodeToString(input))
	return []byte{0}, nil
}

func Run(code []byte, inputs [][]byte, shouldReturneds [][]byte) {
	fmt.Println("-------------------------")
	fmt.Println("code:", len(code), hex.EncodeToString(code))
	address := common.BytesToAddress([]byte("contract"))
	statedb, _ := state.New(types.EmptyRootHash, state.NewDatabaseForTesting())
	statedb.CreateAccount(address)
	statedb.SetCode(address, code)
	statedb.Finalise(true)
	vmctx := vm.BlockContext{
		CanTransfer: func(vm.StateDB, common.Address, *uint256.Int) bool { return true },
		Transfer:    func(vm.StateDB, common.Address, common.Address, *uint256.Int) {},
		BlockNumber: big.NewInt(0),
	}
	config := vm.Config{ExtraEips: []int{3855, 3860}}
	chainConfig := params.AllEthashProtocolChanges
	evm := vm.NewEVM(vmctx, statedb, chainConfig, config)
	chainRules := chainConfig.Rules(vmctx.BlockNumber, vmctx.Random != nil, vmctx.Time) // must be same with the one in vm.NewEVM
	precompiles := vm.ActivePrecompiledContracts(chainRules)

	precompiles[core.PRINT_ADDRESS] = &print{}

	evm.SetPrecompiles(precompiles)

	ill := len(inputs)
	rll := len(shouldReturneds)
	if ill != rll {
		panic("error inputs and returns")
	}
	for i := 0; i < ill; i++ {
		input := inputs[i]
		shouldReturned := shouldReturneds[i]

		fmt.Println("input:", len(input), hex.EncodeToString(input))
		var startGas = uint64(100_0000)
		ret, gas, err := evm.Call(common.Address{}, address, input, startGas, new(uint256.Int))
		if err != nil {
			panic(err)
		}
		gasUsed := startGas - gas
		fmt.Println("done", gasUsed, len(ret), hex.EncodeToString(ret))

		if shouldReturned != nil && !bytes.Equal(shouldReturned, ret) {
			panic(fmt.Errorf("return: %#x != %#x", shouldReturned, ret))
		}
	}
}
